by Ed Harris
In This Chapter
Custom controls, or widgets, are useful when the standard input and display controls cannot provide the user with the most optimal input mechanism. Not too long ago, Windows featured a bare-minimum control palettestatic text control, button, edit box, and list box. The combo box, now considered a basic staple of user interface, was a welcome addition to Windows 3.
Now, of course, there are dozens of built-in controls, and hundreds of third-party add-ons. This chapter focuses on the whens, whys, and hows of extending stock controls and developing brand new controls of your own.
Note:This chapter covers the development techniques needed to create new window behavior. What it doesnt cover is the wisdomand the usability testingrequired to determine whether new window behavior is warranted. Sure, your new widget may display editable information to the user in a highly efficient manner. However, if the user cant understand how to manipulate the control without breaking out the documentation, your application becomes unusable. Make sure that the user has the proper affordances to use your new widget. (Affordance is GUI usability-speak for intuitive understanding.) Generally, its a good idea not to stray too far from the beaten path.
One of the confusing aspects of custom control development is the overloaded use of the word class. When Windows was introduced, class was an underused term, and C++ was merely a toy used by AT&T labs. The Windows architects chose to use the term class to represent the collective behavior of a set of windows. Window class attributes include the class name (such as Edit), icon, background color, cursor, and window procedure.
Object-oriented languages use the term class in a similar way, to identify the set of behavior that a family of code (the class) provides. These two uses overlap in the area of custom control development because the programmatic class is used to implement the behavior of the window class.
The term subclass, in object parlance, means a new class (a child) derived from one or more existing classes. In this naming scheme, the parent class is called the superclass.
In the Windows world, subclassing is the action of modifying the behavior of an existing window. Subclassing is done on a window-by-window basis. Superclassing is the act of creating a new breed of window based on the behavior of an existing type (class) of window. As you will see, subclassing is done extensively by MFC. Although superclassing is possible with MFC, it has some significant drawbacks.
In pure Windows-API based development, window class behavior is provided by the message procedure (message proc). Each message destined for a particular window is sent to a single function, where it is routed to code specific to that message. In the early days of Windows development, message procedures sometimes grew to be thousands of lines long.
MFC replaced the message procedure with the concept of a message map. When an MFC-owned window receives a message, the framework looks up the message map for that window, and routes it to the most-derived handler for that message. MFC uses a single window procedure (AfxWndProc) to receive all messages and route them to the appropriate code. When you call CreateWindow, CreateDialog, or any other API that causes a window to be instantiated, you are subclassing that window procedure with AfxWndProc. From that point on, all messages from the system to that window are routed through the message map for processing. This simplifies subclassing enormously and removes the most tedious and error-prone parts of the process.
When creating a custom class, consider whether an existing class provides any of the functionality you need. For example, if the class requires textual input, subclassing the standard edit class might be appropriate. Extending an existing class is generally much easier than creating a new one from scratch.
Perhaps the seminal subclass example is an edit control that validates or reformats its contents. This control, CZipEdit, will format zip codes into ZIP+4 format when focus leaves the control. The control contains one message map handler, for the WM_KILLFOCUS (OnKillFocus) method.
class CZipEdit : public CEdit
{
// Implementation
public:
virtual void FormatContents (const TCHAR* pszText);
// Message Map
public:
//{{AFX_MSG(CZipEdit)
afx_msg void OnKillFocus (CWnd* pwndNew);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
void CZipEdit::OnKillFocus (CWnd* pwndNew)
{
CString strText;
GetWindowText (strText);
FormatContents (strText);
CEdit::OnKillFocus (pwndNew);
}
The kill focus message handler retrieves the window text, passes it to the FormatContents method, and then invokes the default CEdit kill focus handler. The processing is done prior to the default code so that the new edit control contents (if any) are available to the application when the EN_KILLFOCUS notification is received.
The function FormatContents is responsible for taking a character string, doing any necessary transformations, and then setting the resulting text into the edit control. It has public scope; this allows it to be used as a formatting replacement for SetWindowText.
The implementation for the zip code is rather trivial. It verifies that the first five characters of the zip code are digits, and then inserts a single dash at the fifth character.
void CZipEdit::FormatContents (const TCHAR* pszText)
{
CString strOutput (pszText);
if (strOutput.GetLength() >= 9)
{
BOOL bValidZip = TRUE;
for (int nDigit = 0; nDigit < 5; nDigit++)
{
if (!isdigit (strOutput[nDigit]))
bValidZip = FALSE;
}
if (bValidZip && strOutput[5] != _T(-))
{
if (!ispunct (strOutput[5]) && !isspace (strOutput[5]))
strOutput = strOutput.Left (5) + _T(-) +
ÄstrOutput.Mid (5);
else
strOutput.SetAt (5, _T(-));
}
}
SetWindowText (strOutput);
}